﻿@using Berp;
@helper CallProduction(ProductionRule production)
{
  switch(production.Type)
  {
    case ProductionRuleType.Start:
      @:          self.start_rule(context, '@production.RuleName')
      break;
    case ProductionRuleType.End:
      @:          self.end_rule(context, '@production.RuleName')
      break;
    case ProductionRuleType.Process:
      @:          self.build(context, token)
      break;
  }
}
@helper HandleParserError(IEnumerable<string> expectedTokens, State state)
{<text>state_comment = "State: @state.Id - @Raw(state.Comment)"
        token.detach
        expected_tokens = ["@Raw(string.Join("\", \"", expectedTokens))"]
        error = UnexpectedEOFException(token, expected_tokens, state_comment) if token.eof() else UnexpectedTokenException(token, expected_tokens, state_comment)
        if (self.stop_at_first_error):
            raise error
        self.add_error(context, error)
        return @state.Id</text>}
@helper MatchToken(TokenType tokenType)
{<text>match_@(tokenType)(context, token)</text>}
# This file is generated. Do not edit! Edit gherkin-python.razor instead.
import sys
from collections import deque
from .ast_builder import AstBuilder
from .token_matcher import TokenMatcher
from .token_scanner import TokenScanner
from .errors import UnexpectedEOFException, UnexpectedTokenException, ParserException, CompositeParserException

RULE_TYPE = [
    'None',
    @foreach(var rule in Model.RuleSet.Where(r => !r.TempRule))
    {<text>    '@rule.Name.Replace("#", "_")',  # @rule.ToString(true)
</text>}
]


class ParserContext(object):
    def __init__(self, token_scanner, token_matcher, token_queue, errors):
        self.token_scanner = token_scanner
        self.token_matcher = token_matcher
        self.token_queue = token_queue
        self.errors = errors


class @(Model.ParserClassName)(object):
    def __init__(self, ast_builder=None):
        self.ast_builder = ast_builder if ast_builder is not None else AstBuilder()
        self.stop_at_first_error = False

    def parse(self, token_scanner_or_str, token_matcher=None):
        if sys.version_info < (3, 0):
            token_scanner = TokenScanner(token_scanner_or_str) if isinstance(token_scanner_or_str, basestring) else token_scanner_or_str
        else:
            token_scanner = TokenScanner(token_scanner_or_str) if isinstance(token_scanner_or_str, str) else token_scanner_or_str
        self.ast_builder.reset()
        if token_matcher is None:
            token_matcher = TokenMatcher()
        token_matcher.reset()
        context = ParserContext(
            token_scanner,
            token_matcher,
            deque(),
            [])

        self.start_rule(context, '@Model.RuleSet.StartRule.Name')
        state = 0
        token = None
        while True:
            token = self.read_token(context)
            state = self.match_token(state, token, context)
            if token.eof():
                break

        self.end_rule(context, '@Model.RuleSet.StartRule.Name')

        if context.errors:
            raise CompositeParserException(context.errors)

        return self.get_result()

    def build(self, context, token):
        self.handle_ast_error(context, token, self.ast_builder.build)

    def add_error(self, context, error):
        context.errors.append(error)
        if len(context.errors) > 10:
            raise CompositeParserException(context.errors)

    def start_rule(self, context, rule_type):
        self.handle_ast_error(context, rule_type, self.ast_builder.start_rule)

    def end_rule(self, context, rule_type):
        self.handle_ast_error(context, rule_type, self.ast_builder.end_rule)

    def get_result(self):
        return self.ast_builder.get_result()

    def read_token(self, context):
        if context.token_queue:
            return context.token_queue.popleft()
        else:
            return context.token_scanner.read()
@foreach(var rule in Model.RuleSet.TokenRules)
{<text>
    def match_@(rule.Name.Replace("#", ""))(self, context, token):
        @if (rule.Name != "#EOF")
        {
        @:if token.eof():
        @:    return False
        }
        return self.handle_external_error(context, False, token, context.token_matcher.match_@(rule.Name.Replace("#", "")))</text>
}

    def match_token(self, state, token, context):
        state_map = {
        @foreach(var state in Model.States.Values.Where(s => !s.IsEndState))
        {
        @:    @state.Id: self.match_token_at_@(state.Id),
        }
        }
        if state in state_map:
            return state_map[state](token, context)
        else:
            raise RuntimeError("Unknown state: " + str(state))
@foreach(var state in Model.States.Values.Where(s => !s.IsEndState))
{<text>
    # @Raw(state.Comment)
    def match_token_at_@(state.Id)(self, token, context):
        @foreach(var transition in state.Transitions)
        {
        @:if self.@MatchToken(transition.TokenType):
            if (transition.LookAheadHint != null)
            {
            @:if self.lookahead_@(transition.LookAheadHint.Id)(context, token):
            }
                foreach(var production in transition.Productions)
                {
                    @CallProduction(production)
                }
                @:return @transition.TargetState
        }

        @HandleParserError(state.Transitions.Select(t => "#" + t.TokenType.ToString()).Distinct(), state)</text>
}
@foreach(var lookAheadHint in Model.RuleSet.LookAheadHints)
{
<text>
    def lookahead_@(lookAheadHint.Id)(self, context, currentToken):
        currentToken.detach
        token = None
        queue = []
        match = False
        while True:
            token = self.read_token(context)
            token.detach
            queue.append(token)

            if (@foreach(var tokenType in lookAheadHint.ExpectedTokens) {<text>self.@MatchToken(tokenType) or </text>}False):
                match = True
                break

            if not (@foreach(var tokenType in lookAheadHint.Skip) {<text>self.@MatchToken(tokenType) or </text>}False):
                break

        context.token_queue.extend(queue)

        return match</text>
}

    # private

    def handle_ast_error(self, context, argument, action):
        self.handle_external_error(context, True, argument, action)

    def handle_external_error(self, context, default_value, argument, action):
        if self.stop_at_first_error:
            return action(argument)

        try:
            return action(argument)
        except CompositeParserException as e:
            for error in e.errors:
                self.add_error(context, error)
        except ParserException as e:
            self.add_error(context, e)
        return default_value
